import tkinter as tk
from tkinter import ttk, messagebox
import threading
from selenium import webdriver
from selenium.webdriver.edge.options import Options
from selenium.webdriver.edge.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException
import re
import os
from datetime import datetime
import subprocess
import time
import logging
import socket
import uiautomator2 as u2
import requests  # Добавлен для запросов к API

# Настройка логирования
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Глобальные переменные
successful_attempts = 0
failed_attempts = 0
batch_success_count = 0
processing_thread = None
pause_event = threading.Event()
pause_event.set()
is_processing = False
error_attempt_counter = 0
auto_paused_due_to_wifi = False
log_autoscroll_enabled = True
log_autoscroll_var = None

all_results = []
file_lock = threading.Lock()

def append_full_result(login, password, result):
    all_results.append((login, password, result))

# Пути к файлам
tokens_file = r"C:\Users\1\Desktop\VS 2022\Получение токенов ВК\tokens.txt"
failed_logins_file = r"C:\Users\1\Desktop\VS 2022\Получение токенов ВК\неуспешные логи.txt"
login_pass_file = r"C:\Users\1\Desktop\VS 2022\Получение токенов ВК\log pass.txt"
driver_path = r"C:\Users\1\Desktop\edgedriver_win64\msedgedriver.exe"
last_limit_file = r"last limit.txt"
hide_browser_state_file = r"hide_browser_state.txt"
last_model_file = r"last_model.txt"
run_scenario_start_state_file = r"run_scenario_start_state.txt"
run_scenario_end_state_file = r"run_scenario_end_state.txt"

devices_by_model = {}
selected_model_var = None
selected_wifi_var = None
available_wifi_networks = []

# Функции для работы с лимитом
def get_limit_value():
    try:
        return int(limit_entry.get())
    except ValueError:
        return 0

def save_last_limit():
    try:
        with open(last_limit_file, "w", encoding="utf-8") as f:
            f.write(limit_entry.get())
    except Exception as e:
        log_message(f"Ошибка при сохранении лимита: {e}", log_text, msg_type="error")

def load_last_limit():
    try:
        with open(last_limit_file, "r", encoding="utf-8") as f:
            return f.read().strip()
    except Exception:
        return "0"

# Добавление разделителя токенов
def add_token_separator():
    try:
        with file_lock:
            with open(tokens_file, "a", encoding="utf-8") as f:
                now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                separator = f"\n{'='*30} {now} {'='*30}\n"
                f.write(separator)
    except Exception as e:
        log_message(f"Ошибка при добавлении разделителя: {e}", log_text, msg_type="error")

# Сохранение состояния чекбоксов
def save_hide_browser_state(state: bool):
    try:
        with open(hide_browser_state_file, "w", encoding="utf-8") as f:
            f.write(str(state))
    except Exception as e:
        log_message(f"Ошибка при сохранении состояния чекбокса: {e}", log_text, msg_type="error")

def load_hide_browser_state() -> bool:
    try:
        with open(hide_browser_state_file, "r", encoding="utf-8") as f:
            return f.read().strip().lower() == "true"
    except Exception:
        return False

def hide_browser_state_changed(*args):
    save_hide_browser_state(hide_browser_var.get())

def save_run_scenario_start_state(state: bool):
    try:
        with open(run_scenario_start_state_file, "w", encoding="utf-8") as f:
            f.write(str(state))
    except Exception as e:
        log_message(f"Ошибка при сохранении состояния сценария в начале: {e}", log_text, msg_type="error")

def load_run_scenario_start_state() -> bool:
    try:
        with open(run_scenario_start_state_file, "r", encoding="utf-8") as f:
            return f.read().strip().lower() == "true"
    except Exception:
        return False

def run_scenario_start_state_changed(*args):
    save_run_scenario_start_state(run_scenario_start_var.get())

def save_run_scenario_end_state(state: bool):
    try:
        with open(run_scenario_end_state_file, "w", encoding="utf-8") as f:
            f.write(str(state))
    except Exception as e:
        log_message(f"Ошибка при сохранении состояния сценария в конце: {e}", log_text, msg_type="error")

def load_run_scenario_end_state() -> bool:
    try:
        with open(run_scenario_end_state_file, "r", encoding="utf-8") as f:
            return f.read().strip().lower() == "true"
    except Exception:
        return False

def run_scenario_end_state_changed(*args):
    save_run_scenario_end_state(run_scenario_end_var.get())

def save_last_model(model: str):
    try:
        with open(last_model_file, "w", encoding="utf-8") as f:
            f.write(model)
    except Exception as e:
        log_message(f"Ошибка при сохранении модели: {e}", log_text, msg_type="error")

def load_last_model() -> str:
    try:
        with open(last_model_file, "r", encoding="utf-8") as f:
            return f.read().strip()
    except Exception:
        return ""

def on_model_selected(event=None):
    model = selected_model_var.get()
    save_last_model(model)

# Подключение к сети и Wi-Fi
def is_connected() -> bool:
    try:
        socket.create_connection(("8.8.8.8", 53), timeout=2)
        return True
    except OSError:
        return False

def get_connected_wifi() -> str:
    try:
        output = subprocess.check_output("netsh wlan show interfaces", shell=True, encoding="utf-8")
        for line in output.splitlines():
            if "SSID" in line and "BSSID" not in line:
                parts = line.split(":", 1)
                if len(parts) > 1:
                    ssid = parts[1].strip()
                    return ssid if ssid else "Нет подключения"
        return "Нет подключения"
    except Exception as e:
        return f"Ошибка: {e}"

def get_available_networks() -> list:
    networks = []
    try:
        output = subprocess.check_output("netsh wlan show networks", shell=True, encoding="utf-8")
        for line in output.splitlines():
            if "SSID" in line and ":" in line:
                parts = line.split(":", 1)
                if len(parts) > 1:
                    ssid = parts[1].strip()
                    if ssid and ssid not in networks:
                        networks.append(ssid)
    except Exception as e:
        log_message(f"Ошибка при получении сетей: {e}", log_text, msg_type="error")
    return networks

# Сценарии UIAutomator2
def log_scenario_message(message):
    logging.info(message)

def lower_second_shade_and_toggle_switches(d: u2.Device):
    try:
        log_scenario_message("Опускаем шторку уведомлений...")
        d.open_quick_settings()
        time.sleep(0.3)
        log_scenario_message("Шторка опущена.")
        switch1 = d.xpath('//*[@resource-id="com.android.systemui:id/quick_tile_layout"]/android.widget.Switch[1]')
        if switch1.wait(timeout=3):
            switch1.click()
            log_scenario_message("Первый свитч нажат.")
            time.sleep(0.3)
            switch1.click()
            log_scenario_message("Первый свитч повторно нажат.")
        else:
            log_scenario_message("Первый свитч не найден.")
        time.sleep(0.3)
        switch2 = d.xpath('//*[@resource-id="com.android.systemui:id/quick_tile_layout"]/android.widget.Switch[2]')
        if switch2.wait(timeout=3):
            switch2.click()
            log_scenario_message("Второй свитч нажат.")
        else:
            log_scenario_message("Второй свитч не найден.")
        log_scenario_message("Поднимаем шторку свайпом...")
        d.swipe(500, 1700, 500, 500, duration=0.3)
        time.sleep(0.3)
        log_scenario_message("Шторка поднята.")
    except Exception as e:
        log_scenario_message(f"Ошибка в сценарии: {e}")

def run_flight_mode_scenario():
    chosen_model = selected_model_var.get()
    if not chosen_model:
        log_message("Не выбрана модель для сценария.", log_text, msg_type="error")
        return
    serial = devices_by_model.get(chosen_model)
    if not serial:
        log_message(f"Не найден serial для '{chosen_model}'.", log_text, msg_type="error")
        return
    try:
        log_message(f"Запуск сценария на '{chosen_model}'.", log_text, msg_type="info")
        d = u2.connect(serial)
        log_message(f"Устройство: {d.device_info}", log_text, msg_type="info")
        subprocess.run(["adb", "-s", serial, "shell", "am", "start", "-a", "android.settings.AIRPLANE_MODE_SETTINGS"])
        time.sleep(0.3)
        switch_element = d(resourceId="android:id/switch_widget")
        switch_element.click_exists(timeout=3)
        time.sleep(0.3)
        switch_element.click_exists(timeout=3)
        time.sleep(0.5)
        d.app_start("com.android.settings", ".TetherSettings")
        time.sleep(0.3)
        if d(resourceId="com.android.settings:id/recycler_view").child(index=0).exists(timeout=2):
            d(resourceId="com.android.settings:id/recycler_view").child(index=0).click()
            time.sleep(0.3)
        for _ in range(3):
            d.press("back")
            time.sleep(0.2)
        log_message("Ожидание Wi-Fi на ПК...", log_text, msg_type="info")
        while not is_connected():
            time.sleep(0.3)
        log_message("Wi-Fi подключен.", log_text, msg_type="info")
        time.sleep(1)
    except Exception as e:
        log_message(f"Ошибка сценария: {e}", log_text, msg_type="error")

# Логирование и обновление
def log_message(message, text_widget, msg_type="info"):
    def append_message():
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        full_message = f"[{timestamp}] {message}\n"
        text_widget.config(state=tk.NORMAL)
        if msg_type == "info":
            text_widget.insert(tk.END, full_message, "info")
        elif msg_type == "success":
            text_widget.insert(tk.END, full_message, "success")
        elif msg_type == "error":
            text_widget.insert(tk.END, full_message, "error")
        if text_widget is log_text and log_autoscroll_enabled:
            text_widget.see(tk.END)
        text_widget.config(state=tk.DISABLED)
    text_widget.after(0, append_message)

def append_token(token):
    successful_tokens_text.config(state=tk.NORMAL)
    successful_tokens_text.insert(tk.END, token + "\n")
    successful_tokens_text.config(state=tk.DISABLED)

def append_failed_login(login, password):
    failed_logins_text.config(state=tk.NORMAL)
    failed_logins_text.insert(tk.END, f"{login}:{password}\n")
    failed_logins_text.config(state=tk.DISABLED)

def update_status_labels():
    accounts = read_login_passwords()
    total_accounts = len(accounts)
    remaining = total_accounts - (successful_attempts + failed_attempts)
    success_label.config(text=f"Успешные попытки: {successful_attempts}")
    failure_label.config(text=f"Неуспешные попытки: {failed_attempts}")
    count_label.config(text=f"Всего аккаунтов: {total_accounts}")
    remaining_label.config(text=f"Оставшиеся аккаунты: {remaining}")

# Работа с файлами
def save_token_to_file(token, is_error=False):
    try:
        with file_lock:
            with open(tokens_file, "a", encoding='utf-8') as file:
                if is_error:
                    file.write(f"Ошибка: {token}\n")
                else:
                    file.write(token + "\n")
        log_message(f"{'Ошибка' if is_error else 'Токен'} сохранён: {tokens_file}", log_text, msg_type="info")
        if not is_error:
            append_token(token)
    except Exception as e:
        log_message(f"Ошибка сохранения токена: {e}", log_text, msg_type="error")

def save_failed_login(login, password):
    try:
        with file_lock:
            with open(failed_logins_file, "a", encoding='utf-8') as file:
                file.write(f"{login}:{password}\n")
        log_message(f"Неуспешный логин сохранён: {failed_logins_file}", log_text, msg_type="info")
        append_failed_login(login, password)
    except Exception as e:
        log_message(f"Ошибка сохранения логина: {e}", log_text, msg_type="error")

def read_login_passwords():
    if not os.path.exists(login_pass_file):
        log_message(f"Файл не найден: {login_pass_file}", log_text, msg_type="error")
        return []
    try:
        with open(login_pass_file, "r", encoding='utf-8') as file:
            accounts = [line.strip().split(":", 1) for line in file if line.strip()]
        return accounts
    except Exception as e:
        log_message(f"Ошибка чтения файла: {e}", log_text, msg_type="error")
        return []

# Основная логика через Selenium
def open_browser(login, password):
    global successful_attempts, failed_attempts, batch_success_count
    log_message(f"Открытие браузера для: {login}", log_text, msg_type="info")
    options = Options()
    options.add_argument("--inprivate")
    if hide_browser_var.get():
        options.add_argument("--headless")
        options.add_argument("--disable-gpu")
        log_message("Скрытый режим браузера.", log_text, msg_type="info")
    service = Service(driver_path)
    driver = webdriver.Edge(service=service, options=options)
    try:
        driver.get("https://vkhost.github.io/")
        log_message("Страница vkhost открыта.", log_text, msg_type="info")
        button = WebDriverWait(driver, 15).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button.btn[onclick='auth(6121396)']"))
        )
        button.click()
        log_message("Кнопка авторизации нажата.", log_text, msg_type="info")
        WebDriverWait(driver, 15).until(lambda d: len(d.window_handles) > 1)
        driver.switch_to.window(driver.window_handles[-1])
        log_message(f"Новая вкладка: {driver.current_url}", log_text, msg_type="info")
        WebDriverWait(driver, 15).until(
            lambda d: d.current_url.startswith("https://oauth.vk.com/authorize?")
        )
        log_message(f"Страница ВК: {driver.current_url}", log_text, msg_type="info")
        email_field = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='text'][name='email']"))
        )
        password_field = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='password'][name='pass']"))
        )
        email_field.send_keys(login)
        log_message("Логин введён.", log_text, msg_type="info")
        password_field.send_keys(password)
        log_message("Пароль введён.", log_text, msg_type="info")
        submit_button = WebDriverWait(driver, 15).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button.flat_button.oauth_button.button_wide[type='submit']"))
        )
        submit_button.click()
        log_message("Кнопка 'Войти' нажата.", log_text, msg_type="info")
        try:
            found_error = WebDriverWait(driver, 5).until(
                EC.any_of(
                    EC.presence_of_element_located((By.CSS_SELECTOR, ".box_error")),
                    EC.presence_of_element_located((By.CSS_SELECTOR, ".oauth_captcha"))
                )
            )
            error_class = found_error.get_attribute("class")
            if "box_error" in error_class:
                error_text = "Невалидный логин или пароль."
            elif "oauth_captcha" in error_class:
                error_text = "Найдена капча"
            else:
                error_text = "Неизвестная ошибка"
            failed_attempts += 1
            save_token_to_file(f"{error_text}: {login}", is_error=True)
            save_failed_login(login, password)
            append_full_result(login, password, error_text)
            log_message(f"Ошибка: {error_text}", log_text, msg_type="error")
            append_token(f"{error_text}: {login}")
            return
        except TimeoutException:
            pass
        allow_button = WebDriverWait(driver, 15).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button.flat_button[onclick='return allow(this);']"))
        )
        allow_button.click()
        log_message("Кнопка подтверждения нажата.", log_text, msg_type="info")
        WebDriverWait(driver, 15).until(
            lambda d: d.current_url.startswith("https://oauth.vk.com/blank.html#access_token=")
        )
        log_message(f"Страница с токеном: {driver.current_url}", log_text, msg_type="info")
        url = driver.current_url
        token_match = re.search(r"access_token=([^&]+)&expires_in", url)
        if token_match:
            token = token_match.group(1)
            save_token_to_file(token)
            append_full_result(login, password, token)
            successful_attempts += 1
            batch_success_count += 1
            log_message("Токен получен.", log_text, msg_type="success")
            current_limit = get_limit_value()
            if current_limit > 0 and batch_success_count >= current_limit:
                run_flight_mode_scenario()
                batch_success_count = 0
        else:
            failed_attempts += 1
            save_token_to_file(f"Токен не найден: {login}", is_error=True)
            save_failed_login(login, password)
            append_full_result(login, password, "Токен не найден")
            log_message("Ошибка: Токен не найден.", log_text, msg_type="error")
            append_token(f"Токен не найден: {login}")
    except TimeoutException:
        failed_attempts += 1
        save_token_to_file(f"Timeout: {login}", is_error=True)
        save_failed_login(login, password)
        append_full_result(login, password, "Timeout")
        log_message(f"Ошибка: Timeout. URL: {driver.current_url}", log_text, msg_type="error")
        append_token(f"Timeout: {login}")
    except NoSuchElementException:
        failed_attempts += 1
        save_token_to_file(f"Элемент не найден: {login}", is_error=True)
        save_failed_login(login, password)
        append_full_result(login, password, "Элемент не найден")
        log_message("Ошибка: Элемент не найден.", log_text, msg_type="error")
        append_token(f"Элемент не найден: {login}")
    except Exception as e:
        failed_attempts += 1
        save_token_to_file(f"{str(e)}: {login}", is_error=True)
        save_failed_login(login, password)
        append_full_result(login, password, f"Ошибка: {e}")
        log_message(f"Неожиданная ошибка: {e}", log_text, msg_type="error")
        append_token(f"Неожиданная ошибка: {login}")
    finally:
        driver.quit()
        log_message("Браузер закрыт.", log_text, msg_type="info")
        update_status_labels()

def process_all_accounts():
    global is_processing
    accounts = read_login_passwords()
    for login, password in accounts:
        pause_event.wait()
        if not is_processing:
            break
        log_message(f"Обработка аккаунта: {login}", log_text, msg_type="info")
        open_browser(login, password)
    if is_processing and run_scenario_end_var.get():
        log_message("Запуск сценария в конце.", log_text, msg_type="info")
        run_flight_mode_scenario()
    is_processing = False
    start_button.config(state=tk.NORMAL)
    pause_button.config(state=tk.DISABLED)
    resume_button.config(state=tk.DISABLED)
    log_message("Обработка завершена.", log_text, msg_type="info")

# Управление потоком
def start_browser_thread():
    global is_processing, processing_thread, auto_paused_due_to_wifi
    if is_processing:
        messagebox.showinfo("Информация", "Процесс уже запущен.")
        return
    add_token_separator()
    if selected_wifi_var.get() != get_connected_wifi():
        messagebox.showwarning("Внимание", "Текущая Wi-Fi сеть не совпадает с выбранной.\nЗапуск невозможен.")
        return
    save_last_limit()
    is_processing = True
    pause_event.set()
    auto_paused_due_to_wifi = False
    start_button.config(state=tk.DISABLED)
    pause_button.config(state=tk.NORMAL)
    resume_button.config(state=tk.DISABLED)
    def _run():
        try:
            if run_scenario_start_var.get():
                log_message("Запуск сценария в начале.", log_text, msg_type="info")
                run_flight_mode_scenario()
            process_all_accounts()
        except Exception as ex:
            log_message(f"Ошибка в основном потоке: {ex}", log_text, msg_type="error")
        finally:
            start_button.config(state=tk.NORMAL)
            pause_button.config(state=tk.DISABLED)
            resume_button.config(state=tk.DISABLED)
    processing_thread = threading.Thread(target=_run, daemon=True)
    processing_thread.start()

def pause_processing():
    global auto_paused_due_to_wifi
    if not is_processing:
        return
    pause_event.clear()
    auto_paused_due_to_wifi = False
    log_message("Процесс приостановлен (вручную).", log_text, msg_type="info")
    pause_button.config(state=tk.DISABLED)
    resume_button.config(state=tk.NORMAL)

def resume_processing():
    if not is_processing:
        return
    if selected_wifi_var.get() != get_connected_wifi():
        messagebox.showwarning("Внимание", "Невозможно возобновить: текущая Wi-Fi сеть не совпадает с выбранной.")
        return
    pause_event.set()
    log_message("Процесс восстановлен (вручную).", log_text, msg_type="info")
    pause_button.config(state=tk.NORMAL)
    resume_button.config(state=tk.DISABLED)

# Функции копирования
def copy_successful_tokens():
    content = successful_tokens_text.get("1.0", tk.END).strip()
    if not content:
        messagebox.showinfo("Информация", "Нет токенов для копирования.")
        return
    root.clipboard_clear()
    root.clipboard_append(content)
    log_message("Токены скопированы в буфер обмена.", log_text, msg_type="info")

def copy_failed_logins():
    content = failed_logins_text.get("1.0", tk.END).strip()
    if not content:
        messagebox.showinfo("Информация", "Нет неуспешных логинов для копирования.")
        return
    root.clipboard_clear()
    root.clipboard_append(content)
    log_message("Неуспешные логины и пароли скопированы в буфер обмена.", log_text, msg_type="info")

def copy_all():
    if not all_results:
        messagebox.showinfo("Информация", "Нет данных для копирования.")
        return
    lines = [f"{login}:{password}\t{result}" for login, password, result in all_results]
    text = "\n".join(lines)
    root.clipboard_clear()
    root.clipboard_append(text)
    log_message("Список логин:пароль и результатов скопирован.", log_text, msg_type="info")

def copy_selection():
    try:
        selected_text = root.focus_get().get(tk.SEL_FIRST, tk.SEL_LAST)
        root.clipboard_clear()
        root.clipboard_append(selected_text)
    except tk.TclError:
        pass

def select_all(text_widget):
    text_widget.tag_add(tk.SEL, "1.0", tk.END)
    text_widget.mark_set(tk.INSERT, "1.0")
    text_widget.see(tk.INSERT)

# Автопрокрутка
def toggle_log_autoscroll():
    global log_autoscroll_enabled
    log_autoscroll_enabled = log_autoscroll_var.get()

# Контекстные меню
def show_log_context_menu(event):
    log_context_menu.tk_popup(event.x_root, event.y_root)

def show_text_context_menu(event):
    text_context_menu.tk_popup(event.x_root, event.y_root)

# Сканирование ADB
def scan_devices():
    global devices_by_model
    devices_by_model.clear()
    try:
        result = subprocess.run(["adb", "devices", "-l"], capture_output=True, text=True, check=False)
        output = result.stdout.strip().splitlines()
        for line in output[1:]:
            line = line.strip()
            if not line or "offline" in line or "unauthorized" in line or "unknown" in line:
                continue
            parts = line.split()
            serial = parts[0]
            model = None
            for p in parts:
                if p.startswith("model:"):
                    model = p.split(":", 1)[1]
                    break
            if not model:
                model = "UnknownModel"
            devices_by_model[model] = serial
        log_message(f"Устройства обновлены: {devices_by_model}", log_text, msg_type="info")
    except Exception as e:
        log_message(f"Ошибка при сканировании устройств: {e}", log_text, msg_type="error")
    model_list = list(devices_by_model.keys())
    current_selection = load_last_model()
    def update_gui():
        device_combobox["values"] = model_list
        if current_selection in model_list:
            selected_model_var.set(current_selection)
        elif model_list:
            selected_model_var.set(model_list[0])
        else:
            selected_model_var.set("Нет подключённых устройств")
    root.after(0, update_gui)

def scan_devices_async():
    threading.Thread(target=scan_devices, daemon=True).start()

# Обновление списка Wi-Fi
def refresh_wifi_list():
    global available_wifi_networks
    available_wifi_networks = get_available_networks()
    current_ssid = get_connected_wifi()
    def update_wifi_combobox():
        wifi_combobox["values"] = available_wifi_networks
        if current_ssid in available_wifi_networks:
            selected_wifi_var.set(current_ssid)
    root.after(0, update_wifi_combobox)

def refresh_wifi_async():
    threading.Thread(target=refresh_wifi_list, daemon=True).start()

# Окна для просмотра/очистки
def open_tokens_window():
    if hasattr(open_tokens_window, 'window') and open_tokens_window.window.winfo_exists():
        open_tokens_window.window.focus()
        return
    tokens_window = tk.Toplevel(root)
    tokens_window.title("Успешные токены")
    tokens_window.geometry("600x400")
    tokens_window.configure(bg='#1e222a')
    frame = ttk.Frame(tokens_window, padding="10", style='Card.TFrame')
    frame.pack(fill=tk.BOTH, expand=True)
    tokens_text = tk.Text(frame, height=20, width=70, state=tk.DISABLED, wrap=tk.WORD, bg='#2a2f38', fg='#b8c0d0', insertbackground='white', font=('Segoe UI', 10))
    tokens_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=tokens_text.yview, style='Modern.Vertical.TScrollbar')
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    tokens_text.config(yscrollcommand=scrollbar.set)
    def load_tokens():
        tokens_text.config(state=tk.NORMAL)
        tokens_text.delete("1.0", tk.END)
        if os.path.exists(tokens_file):
            with open(tokens_file, "r", encoding='utf-8') as file:
                tokens = file.read()
                tokens_text.insert(tk.END, tokens)
        else:
            tokens_text.insert(tk.END, "Файл с токенами не найден.")
        tokens_text.config(state=tk.DISABLED)
    def clear_tokens():
        if messagebox.askyesno("Подтверждение", "Вы уверены, что хотите очистить все токены?"):
            try:
                open(tokens_file, "w", encoding='utf-8').close()
                tokens_text.config(state=tk.NORMAL)
                tokens_text.delete("1.0", tk.END)
                tokens_text.config(state=tk.DISABLED)
                log_message("Все токены очищены.", log_text, msg_type="info")
                successful_tokens_text.config(state=tk.NORMAL)
                successful_tokens_text.delete("1.0", tk.END)
                successful_tokens_text.config(state=tk.DISABLED)
                update_status_labels()
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось очистить токены: {e}")
    load_tokens()
    button_frame = ttk.Frame(tokens_window, style='Card.TFrame')
    button_frame.pack(pady=5)
    clear_button = ttk.Button(button_frame, text="Очистить", command=clear_tokens, style='Accent.TButton')
    clear_button.pack()
    open_tokens_window.window = tokens_window

def open_failed_logins_window():
    if hasattr(open_failed_logins_window, 'window') and open_failed_logins_window.window.winfo_exists():
        open_failed_logins_window.window.focus()
        return
    failed_window = tk.Toplevel(root)
    failed_window.title("Неуспешные логины и пароли")
    failed_window.geometry("600x400")
    failed_window.configure(bg='#1e222a')
    frame = ttk.Frame(failed_window, padding="10", style='Card.TFrame')
    frame.pack(fill=tk.BOTH, expand=True)
    failed_text = tk.Text(frame, height=20, width=70, state=tk.DISABLED, wrap=tk.WORD, bg='#2a2f38', fg='#b8c0d0', insertbackground='white', font=('Segoe UI', 10))
    failed_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=failed_text.yview, style='Modern.Vertical.TScrollbar')
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    failed_text.config(yscrollcommand=scrollbar.set)
    def load_failed_logins():
        failed_text.config(state=tk.NORMAL)
        failed_text.delete("1.0", tk.END)
        if os.path.exists(failed_logins_file):
            with open(failed_logins_file, "r", encoding='utf-8') as file:
                failed_logins = file.read()
                failed_text.insert(tk.END, failed_logins)
        else:
            failed_text.insert(tk.END, "Файл с неуспешными логинами не найден.")
        failed_text.config(state=tk.DISABLED)
    def clear_failed_logins():
        if messagebox.askyesno("Подтверждение", "Вы уверены, что хотите очистить все неуспешные логины и пароли?"):
            try:
                open(failed_logins_file, "w", encoding='utf-8').close()
                failed_text.config(state=tk.NORMAL)
                failed_text.delete("1.0", tk.END)
                failed_text.config(state=tk.DISABLED)
                log_message("Все неуспешные логины и пароли очищены.", log_text, msg_type="info")
                failed_logins_text.config(state=tk.NORMAL)
                failed_logins_text.delete("1.0", tk.END)
                failed_logins_text.config(state=tk.DISABLED)
                update_status_labels()
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось очистить неуспешные логины: {e}")
    load_failed_logins()
    button_frame = ttk.Frame(failed_window, style='Card.TFrame')
    button_frame.pack(pady=5)
    clear_button = ttk.Button(button_frame, text="Очистить", command=clear_failed_logins, style='Accent.TButton')
    clear_button.pack()
    open_failed_logins_window.window = failed_window

def clear_all_logs():
    if messagebox.askyesno("Подтверждение", "Вы уверены, что хотите очистить все журналы, токены и неуспешные логины?"):
        try:
            open(tokens_file, "w", encoding='utf-8').close()
            open(failed_logins_file, "w", encoding='utf-8').close()
            log_text.config(state=tk.NORMAL)
            log_text.delete("1.0", tk.END)
            log_text.config(state=tk.DISABLED)
            successful_tokens_text.config(state=tk.NORMAL)
            successful_tokens_text.delete("1.0", tk.END)
            successful_tokens_text.config(state=tk.DISABLED)
            failed_logins_text.config(state=tk.NORMAL)
            failed_logins_text.delete("1.0", tk.END)
            failed_logins_text.config(state=tk.DISABLED)
            global successful_attempts, failed_attempts
            successful_attempts = 0
            failed_attempts = 0
            all_results.clear()
            update_status_labels()
            log_message("Все журналы и данные очищены.", log_text, msg_type="info")
            if hasattr(open_tokens_window, 'window') and open_tokens_window.window.winfo_exists():
                open_tokens_window.window.destroy()
            if hasattr(open_failed_logins_window, 'window') and open_failed_logins_window.window.winfo_exists():
                open_failed_logins_window.window.destroy()
        except Exception as e:
            messagebox.showerror("Ошибка", f"Не удалось очистить журналы: {e}")

def restart_failed_accounts():
    global error_attempt_counter, successful_attempts, failed_attempts, batch_success_count
    successful_attempts = 0
    failed_attempts = 0
    batch_success_count = 0
    error_attempt_counter = 0
    all_results.clear()
    update_status_labels()
    successful_tokens_text.config(state=tk.NORMAL)
    successful_tokens_text.delete("1.0", tk.END)
    successful_tokens_text.config(state=tk.DISABLED)
    content = failed_logins_text.get("1.0", tk.END).strip()
    if not content:
        messagebox.showinfo("Информация", "Нет аккаунтов для перезапуска.")
        return
    failed_logins_text.config(state=tk.NORMAL)
    failed_logins_text.delete("1.0", tk.END)
    failed_logins_text.config(state=tk.DISABLED)
    try:
        with open(failed_logins_file, "w", encoding="utf-8") as f:
            f.write("")
    except Exception as e:
        log_message(f"Ошибка при очистке файла неуспешных логинов: {e}", log_text, msg_type="error")
    def process_failed():
        lines = content.splitlines()
        for line in lines:
            parts = line.split(":", 1)
            if len(parts) == 2:
                login, password = parts
                log_message(f"Перезапуск аккаунта: {login}", log_text, msg_type="info")
                open_browser(login, password)
        update_status_labels()
    threading.Thread(target=process_failed, daemon=True).start()

# Авто-проверка Wi-Fi
def update_wifi_status():
    global auto_paused_due_to_wifi
    current_wifi = get_connected_wifi()
    wifi_status_label.config(text=f"Подключенный Wi-Fi: {current_wifi}")
    selected_wifi = selected_wifi_var.get()
    if selected_wifi and selected_wifi != current_wifi:
        if is_processing and pause_event.is_set():
            pause_event.clear()
            auto_paused_due_to_wifi = True
            log_message("Процесс приостановлен автоматически (Wi-Fi mismatch).", log_text, msg_type="info")
        if not is_processing:
            start_button.config(state=tk.DISABLED)
    else:
        if not is_processing:
            start_button.config(state=tk.NORMAL)
        else:
            if auto_paused_due_to_wifi and not pause_event.is_set():
                pause_event.set()
                auto_paused_due_to_wifi = False
                log_message("Процесс возобновлён автоматически (Wi-Fi соответствует).", log_text, msg_type="info")
                time.sleep(0.2)
    root.after(2000, update_wifi_status)

def on_wifi_selected(event=None):
    global auto_paused_due_to_wifi
    selected = selected_wifi_var.get()
    current_wifi = get_connected_wifi()
    if selected and selected != current_wifi:
        if is_processing and pause_event.is_set():
            pause_event.clear()
            auto_paused_due_to_wifi = True
            log_message("Процесс приостановлен автоматически (Wi-Fi mismatch).", log_text, msg_type="info")
        if not is_processing:
            start_button.config(state=tk.DISABLED)
    else:
        if not is_processing:
            start_button.config(state=tk.NORMAL)
        else:
            if auto_paused_due_to_wifi and not pause_event.is_set():
                pause_event.set()
                auto_paused_due_to_wifi = False
                log_message("Процесс возобновлён автоматически (Wi-Fi выбран совпадает с текущей).", log_text, msg_type="info")
                time.sleep(0.2)

# **Добавленные функции для IP и локации**
def get_external_ip():
    """Получает внешний IP-адрес через публичный API."""
    try:
        response = requests.get('https://api.ipify.org', timeout=5)
        response.raise_for_status()
        return response.text.strip()
    except requests.RequestException:
        return "Недоступно"

def get_location_by_ip(ip):
    """Получает локацию по IP через ipinfo.io."""
    try:
        response = requests.get(f'https://ipinfo.io/{ip}/json', timeout=5)
        response.raise_for_status()
        data = response.json()
        city = data.get('city', 'Неизвестно')
        region = data.get('region', 'Неизвестно')
        country = data.get('country', 'Неизвестно')
        return f"{city}, {region}, {country}"
    except requests.RequestException:
        return "Недоступно"

def update_ip_location_display():
    """Обновляет отображение IP и локации каждые 15 секунд."""
    ip = get_external_ip()
    current_ip_var.set(f"IP: {ip}")
    if ip != "Недоступно":
        location = get_location_by_ip(ip)
        current_location_var.set(f"Локация: {location}")
    else:
        current_location_var.set("Локация: Недоступно")
    root.after(15000, update_ip_location_display)  # Обновление каждые 15 секунд

# Интерфейс Tkinter
root = tk.Tk()
root.title("Получение токенов ВК")
root.resizable(True, True)
root.geometry("1350x850")
root.configure(bg='#1e222a')

# Настройка современного стиля
style = ttk.Style()
style.theme_use('clam')

# Стили для кнопок
style.configure('TButton', background='#3e4656', foreground='#b8c0d0', bordercolor='#4e566a', padding=8, font=('Segoe UI', 10), relief='flat')
style.map('TButton', background=[('active', '#4e566a'), ('disabled', '#2a2f38')], foreground=[('disabled', '#6b7280')])
style.configure('Accent.TButton', background='#5b9bd5', foreground='#ffffff', bordercolor='#5b9bd5', padding=8, font=('Segoe UI', 10), relief='flat')
style.map('Accent.TButton', background=[('active', '#4a8ac5'), ('disabled', '#2a2f38')], foreground=[('disabled', '#6b7280')])

# Стили для меток и фреймов
style.configure('TLabel', background='#1e222a', foreground='#b8c0d0', font=('Segoe UI', 10))
style.configure('TCheckbutton', background='#1e222a', foreground='#b8c0d0', font=('Segoe UI', 10))
style.configure('TCombobox', fieldbackground='#2a2f38', background='#3e4656', foreground='white', font=('Segoe UI', 10))
style.map('TCombobox', fieldbackground=[('readonly', '#2a2f38')], selectbackground=[('readonly', '#2a2f38')], selectforeground=[('readonly', '#000000')])
style.configure('TEntry', fieldbackground='#2a2f38', foreground='#b8c0d0', insertcolor='white', font=('Segoe UI', 10))
style.configure('TFrame', background='#1e222a')
style.configure('TLabelframe', background='#1e222a', foreground='#b8c0d0', font=('Segoe UI', 10, 'bold'))
style.configure('TLabelframe.Label', background='#1e222a', foreground='#b8c0d0')

# Стиль для карточек (фреймы с тенью)
style.configure('Card.TFrame', background='#262a33', bordercolor='#3e4656', relief='flat')

# Стиль для скроллбаров
style.configure('Modern.Vertical.TScrollbar', background='#3e4656', troughcolor='#1e222a', arrowcolor='#b8c0d0', relief='flat')
style.map('Modern.Vertical.TScrollbar', background=[('active', '#4e566a')])

# Стиль для разделителя
style.configure('Vertical.TSeparator', background='#4e566a')

main_frame = ttk.Frame(root, padding="10", style='Card.TFrame')
main_frame.pack(fill=tk.BOTH, expand=True)

# ------- Панель управления -------
control_section = ttk.LabelFrame(main_frame, text="Панель управления", padding="10")
control_section.pack(fill=tk.X, pady=5)

control_row1 = ttk.Frame(control_section)
control_row1.pack(fill=tk.X, padx=5, pady=5)

start_button = ttk.Button(control_row1, text="Запустить", command=start_browser_thread, style='Accent.TButton')
start_button.pack(side=tk.LEFT, padx=5)

pause_button = ttk.Button(control_row1, text="Приостановить", command=pause_processing, state=tk.DISABLED)
pause_button.pack(side=tk.LEFT, padx=5)

resume_button = ttk.Button(control_row1, text="Восстановить", command=resume_processing, state=tk.DISABLED)
resume_button.pack(side=tk.LEFT, padx=5)

restart_button = ttk.Button(control_row1, text="Перезапустить ошибки", command=restart_failed_accounts)
restart_button.pack(side=tk.LEFT, padx=5)

separator = ttk.Separator(control_row1, orient='vertical', style='Vertical.TSeparator')
separator.pack(side=tk.LEFT, padx=10, fill='y')

update_labels_button = ttk.Button(control_row1, text="Обновить метки", command=update_status_labels)
update_labels_button.pack(side=tk.LEFT, padx=5)

clear_logs_button = ttk.Button(control_row1, text="Очистить все журналы", command=clear_all_logs)
clear_logs_button.pack(side=tk.LEFT, padx=5)

control_row2 = ttk.Frame(control_section)
control_row2.pack(fill=tk.X, padx=5, pady=5)

hide_browser_var = tk.BooleanVar()
hide_browser_var.set(load_hide_browser_state())
hide_browser_var.trace_add("write", hide_browser_state_changed)
hide_browser_check = ttk.Checkbutton(control_row2, text="Скрыть браузер", variable=hide_browser_var)
hide_browser_check.pack(side=tk.LEFT, padx=5)

run_scenario_start_var = tk.BooleanVar()
run_scenario_start_var.set(load_run_scenario_start_state())
run_scenario_start_var.trace_add("write", run_scenario_start_state_changed)
run_scenario_start_check = ttk.Checkbutton(control_row2, text="Запустить сценарий в начале", variable=run_scenario_start_var)
run_scenario_start_check.pack(side=tk.LEFT, padx=5)

run_scenario_end_var = tk.BooleanVar()
run_scenario_end_var.set(load_run_scenario_end_state())
run_scenario_end_var.trace_add("write", run_scenario_end_state_changed)
run_scenario_end_check = ttk.Checkbutton(control_row2, text="Запустить сценарий в конце", variable=run_scenario_end_var)
run_scenario_end_check.pack(side=tk.LEFT, padx=5)

separator = ttk.Separator(control_row2, orient='vertical', style='Vertical.TSeparator')
separator.pack(side=tk.LEFT, padx=10, fill='y')

limit_label = ttk.Label(control_row2, text="Лимит токенов:")
limit_label.pack(side=tk.LEFT, padx=5)
limit_entry = ttk.Entry(control_row2, width=5)
limit_entry.pack(side=tk.LEFT)
limit_entry.bind("<FocusOut>", lambda e: save_last_limit())
limit_entry.insert(0, load_last_limit())

control_row3 = ttk.Frame(control_section)
control_row3.pack(fill=tk.X, padx=5, pady=5)

selected_model_var = tk.StringVar()
device_combobox = ttk.Combobox(control_row3, textvariable=selected_model_var, state="readonly", width=30)
device_combobox.pack(side=tk.LEFT, padx=5)
device_combobox.set("Нет подключённых устройств")
device_combobox.bind("<<ComboboxSelected>>", on_model_selected)

scan_devices_button = ttk.Button(control_row3, text="Сканировать устройства", command=scan_devices_async)
scan_devices_button.pack(side=tk.LEFT, padx=5)

separator = ttk.Separator(control_row3, orient='vertical', style='Vertical.TSeparator')
separator.pack(side=tk.LEFT, padx=10, fill='y')

wifi_label = ttk.Label(control_row3, text="Выбрать Wi-Fi:")
wifi_label.pack(side=tk.LEFT, padx=5)

selected_wifi_var = tk.StringVar()
wifi_combobox = ttk.Combobox(control_row3, textvariable=selected_wifi_var, state="readonly", width=30)
wifi_combobox.pack(side=tk.LEFT, padx=5)
wifi_combobox.bind("<<ComboboxSelected>>", on_wifi_selected)

refresh_wifi_button = ttk.Button(control_row3, text="Обновить Wi-Fi", command=refresh_wifi_async)
refresh_wifi_button.pack(side=tk.LEFT, padx=5)

# ------- Статус -------
status_section = ttk.LabelFrame(main_frame, text="Статус", padding="10")
status_section.pack(fill=tk.X, pady=5)

status_frame = ttk.Frame(status_section)
status_frame.pack(fill=tk.X, padx=5, pady=5)

success_label = ttk.Label(status_frame, text="Успешные попытки: 0", foreground="#89ca78", cursor="hand2")
success_label.pack(side=tk.LEFT, padx=10)
success_label.bind("<Button-1>", lambda e: copy_successful_tokens())

failure_label = ttk.Label(status_frame, text="Неуспешные попытки: 0", foreground="#e57373", cursor="hand2")
failure_label.pack(side=tk.LEFT, padx=10)
failure_label.bind("<Button-1>", lambda e: copy_failed_logins())

count_label = ttk.Label(status_frame, text="Всего аккаунтов: 0", foreground="#5b9bd5")
count_label.pack(side=tk.LEFT, padx=10)

remaining_label = ttk.Label(status_frame, text="Оставшиеся аккаунты: 0", foreground="#f4a261")
remaining_label.pack(side=tk.LEFT, padx=10)

copy_all_label = ttk.Label(status_frame, text="Copy all", foreground="#b8c0d0", cursor="hand2")
copy_all_label.pack(side=tk.LEFT, padx=10)
copy_all_label.bind("<Button-1>", lambda e: copy_all())

wifi_status_label = ttk.Label(status_frame, text="Подключенный Wi-Fi: Неизвестно", foreground="#bb77d1")
wifi_status_label.pack(side=tk.LEFT, padx=10)

# **Добавление меток для IP и локации**
current_ip_var = tk.StringVar(value="IP: Сканирование...")
current_location_var = tk.StringVar(value="Локация: Сканирование...")
ip_label_ui = ttk.Label(status_frame, textvariable=current_ip_var, font=('Segoe UI', 10))
ip_label_ui.pack(side=tk.LEFT, padx=10)
location_label_ui = ttk.Label(status_frame, textvariable=current_location_var, font=('Segoe UI', 10))
location_label_ui.pack(side=tk.LEFT, padx=10)

# ------- Основная область (лог, токены и неуспешные) -------
logs_frame = ttk.Frame(main_frame, style='Card.TFrame')
logs_frame.pack(fill=tk.BOTH, expand=True, pady=10)

log_frame = ttk.LabelFrame(logs_frame, text="Журнал действий")
log_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
log_text = tk.Text(log_frame, height=20, width=40, state=tk.DISABLED, wrap=tk.WORD, bg='#2a2f38', fg='#b8c0d0', insertbackground='white', font=('Segoe UI', 10))
log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
log_scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=log_text.yview, style='Modern.Vertical.TScrollbar')
log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
log_text.config(yscrollcommand=log_scrollbar.set)
log_text.tag_config("info", foreground="#b8c0d0")
log_text.tag_config("success", foreground="#89ca78")
log_text.tag_config("error", foreground="#e57373")

tokens_frame = ttk.LabelFrame(logs_frame, text="Успешные токены")
tokens_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
successful_tokens_text = tk.Text(tokens_frame, height=20, width=40, state=tk.DISABLED, wrap=tk.WORD, bg='#2a2f38', fg='#b8c0d0', insertbackground='white', font=('Segoe UI', 10))
successful_tokens_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
tokens_scrollbar = ttk.Scrollbar(tokens_frame, orient=tk.VERTICAL, command=successful_tokens_text.yview, style='Modern.Vertical.TScrollbar')
tokens_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
successful_tokens_text.config(yscrollcommand=tokens_scrollbar.set)
successful_tokens_text.tag_config("success", foreground="#89ca78")
successful_tokens_text.tag_config("error", foreground="#e57373")

failed_frame = ttk.LabelFrame(main_frame, text="Неуспешные логины и пароли")
failed_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=10)
failed_logins_text = tk.Text(failed_frame, height=10, width=80, state=tk.DISABLED, wrap=tk.WORD, bg='#2a2f38', fg='#b8c0d0', insertbackground='white', font=('Segoe UI', 10))
failed_logins_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
failed_scrollbar = ttk.Scrollbar(failed_frame, orient=tk.VERTICAL, command=failed_logins_text.yview, style='Modern.Vertical.TScrollbar')
failed_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
failed_logins_text.config(yscrollcommand=failed_scrollbar.set)
failed_logins_text.tag_config("error", foreground="#e57373")

# ------- Контекстные меню -------
log_context_menu = tk.Menu(root, tearoff=0, bg='#3e4656', fg='#b8c0d0', activebackground='#4e566a', activeforeground='#ffffff', font=('Segoe UI', 10))
log_autoscroll_var = tk.BooleanVar(value=True)
log_context_menu.add_command(label="Копировать", command=copy_selection)
log_context_menu.add_command(label="Выбрать всё", command=lambda: select_all(log_text))
log_context_menu.add_separator()
log_context_menu.add_checkbutton(
    label="Автопрокрутка",
    variable=log_autoscroll_var,
    onvalue=True,
    offvalue=False,
    command=toggle_log_autoscroll,
    background='#3e4656',
    foreground='#b8c0d0',
    activebackground='#4e566a',
    activeforeground='#ffffff'
)

text_context_menu = tk.Menu(root, tearoff=0, bg='#3e4656', fg='#b8c0d0', activebackground='#4e566a', activeforeground='#ffffff', font=('Segoe UI', 10))
text_context_menu.add_command(label="Копировать", command=copy_selection)
text_context_menu.add_command(label="Выбрать всё", command=lambda: select_all(root.focus_get()))

log_text.bind("<Button-3>", show_log_context_menu)
successful_tokens_text.bind("<Button-3>", show_text_context_menu)
failed_logins_text.bind("<Button-3>", show_text_context_menu)

# ------- Инициализация -------
log_message("Журнал действий:", log_text, msg_type="info")
update_status_labels()
scan_devices_async()   # Сканируем устройства в отдельном потоке
refresh_wifi_async()   # Обновляем список Wi-Fi в отдельном потоке
update_wifi_status()   # Запускаем цикл проверки Wi-Fi (каждые 2000 мс)
update_ip_location_display()  # Запускаем цикл обновления IP и локации (каждые 15 секунд)

root.mainloop()